#include "napi/native_api.h"
#include <hilog/log.h>
#include <object_wrap.h>

napi_ref MyObject::constructor_ref = nullptr;

MyObject::MyObject(double num1, double num2) : a(num1), b(num2), env_(nullptr), wrapped_obj(nullptr) {}

MyObject::~MyObject() {
  napi_delete_reference(env_, wrapped_obj);
}

/**
 * 定义并导出JS类，持久化保存JS类的构造函数
 * @param env
 * @param exports
 */
void MyObject::Init(napi_env env, napi_value exports) {
  OH_LOG_INFO(LOG_APP, "=== MyObject::Init");

  napi_property_descriptor classDesc[] = {
      {"operator", nullptr, MyObject::JSOperate, nullptr, nullptr, nullptr, napi_default, nullptr}};
  
  napi_value constructor = nullptr;
  const char *className = "MyObject";

  // 根据C++类MyObject定义JS类，JS类的构造函数保存在constructor
  napi_define_class(env, className, sizeof(className), MyObject::JSConstructor, nullptr, sizeof(classDesc) / sizeof(classDesc[0]), classDesc, &constructor);

  // 将constructor保存起来，方便以后直接创建js类的实例对象
  napi_create_reference(env, constructor, 1, &constructor_ref);
  
  // 将constructor绑定在导出对象exports上，即 exports.className = constructor
  napi_set_named_property(env, exports, className, constructor);
}

/**
 * new一个新的JS对象时实际的构造函数
 * @param env
 * @param info
 * @return 返回调用JS构造函数，new一个新对象时的this参数
 */
napi_value MyObject::JSConstructor(napi_env env, napi_callback_info info) {
  OH_LOG_INFO(LOG_APP, "=== MyObject::JSConstructor");
  
  // 获取函数的内部属性new.target
  napi_value new_target = nullptr;
  napi_get_new_target(env, info, &new_target);
  // 检测函数是否使用 new 关键字调用
  bool is_constructor = (new_target != nullptr);

  size_t argc = 2;
  napi_value argv[2] = {nullptr};
  napi_value _this = nullptr;

  // 获取构造函数入参
  napi_get_cb_info(env, info, &argc, argv, &_this, nullptr);

  // 设置JS对象的属性, _this.num1 = argv[0]...
  napi_set_named_property(env, _this, "num1", argv[0]);
  napi_set_named_property(env, _this, "num2", argv[1]);

  if (is_constructor) {
    double a, b;
    // 将从JS侧收到的参数格式转换为double
    napi_get_value_double(env, argv[0], &a);
    napi_get_value_double(env, argv[1], &b);

    // 创建类MyObject的实例
    MyObject *obj = new MyObject(a, b);
    obj->env_ = env;

    // 绑定JS对象和C++ Native对象
    napi_wrap(env, _this, obj, MyObject::Destructor, nullptr, &obj->wrapped_obj);

    // 返回绑定后的对象的this
    return _this;
  }
  return nullptr;
}

// 释放资源的函数(类似类的析构函数)
void MyObject::Destructor(napi_env env, void *nativeObject, void *finalize_hint) {
  OH_LOG_INFO(LOG_APP, "=== MyObject::Destructor");
  MyObject *obj = static_cast<MyObject *>(nativeObject);
  delete obj;
}

// JS调用类的方法
napi_value MyObject::JSOperate(napi_env env, napi_callback_info info) {
  OH_LOG_INFO(LOG_APP, "=== MyObject::JSOperate");
  napi_value argv[1] = {nullptr};
  size_t argc = 1;
  napi_value this_ = nullptr;
  
  napi_get_cb_info(env, info, &argc, argv, &this_, nullptr);

  MyObject *myObject;

  // 获取JS对象对应的C++ Native对象
  napi_unwrap(env, this_, reinterpret_cast<void **>(&myObject));

  // 将收到的JS参数保存在operatorType
  uint32_t operatorType = 0;
  napi_get_value_uint32(env, argv[0], &operatorType);

  double result = 0;
  if (operatorType == 0) {
    result = myObject->a + myObject->b;
  } else if (operatorType == 1) {
    result = myObject->a - myObject->b;
  } else if (operatorType == 2) {
    result = myObject->a * myObject->b;
  } else {
    if (myObject->b == 0) {
      result = 0;
    } else {
      result = myObject->a / myObject->b;
    }
  }
  // 将结果转成napi_value类型返回
  napi_value ans = nullptr;
  napi_create_double(env, result, &ans);

  return ans;
}

// 创建一个类的实例，并返回到JS侧
napi_value GetNewInstance(napi_env env, napi_callback_info info) {
  OH_LOG_INFO(LOG_APP, "=== MyObject::CreateJSInstance");

  size_t argc = 2;
  napi_value argv[2] = {nullptr};

  // 获取构造函数入参
  napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);

  // 获取napi_define_class定义JS时保存的constructor_ref
  napi_value constructor = nullptr;
  napi_get_reference_value(env, MyObject::constructor_ref, &constructor);

  // 利于保存的js类构造函数直接创建实例对象
  napi_value instance;
  napi_new_instance(env, constructor, argc, argv, &instance);

  return instance;
}

EXTERN_C_START
napi_value Init(napi_env env, napi_value exports) {
  // 初始化JS类
  MyObject::Init(env, exports);
  
  napi_property_descriptor desc[] = {
      {"getNewInstance", nullptr, GetNewInstance, nullptr, nullptr, nullptr, napi_default, nullptr}};
  
  // 在导出文件index.d.ts里，getNewInstance函数没有写在类内部，需要额外绑定到exports上
  napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);

  return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void) {
  napi_module_register(&demoModule);
}
